package com.rattsoftware.server.serviceImpl;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.mail.MailException;
import org.springframework.security.annotation.Secured;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.rattsoftware.server.admin.model.Role;
import com.rattsoftware.server.admin.model.User;
import com.rattsoftware.server.admin.model.exceptions.RattsSecurityException;
import com.rattsoftware.server.admin.model.exceptions.RattsSessionExpiredException;
import com.rattsoftware.server.admin.model.exceptions.UnexpectedException;
import com.rattsoftware.server.admin.model.exceptions.UserNotFoundException;
import com.rattsoftware.server.admin.model.paging.PagingLoadConfig;
import com.rattsoftware.server.admin.model.paging.PagingLoadResult;
import com.rattsoftware.server.dao.RoleDAO;
import com.rattsoftware.server.dao.SettingDAO;
import com.rattsoftware.server.dao.UserDAO;
import com.rattsoftware.server.security.RattsSessionRegistry;
import com.rattsoftware.server.security.util.RattsSecurityUtil;
import com.rattsoftware.server.service.UserService;
import com.rattsoftware.server.util.OpenXDataUtil;
import com.rattsoftware.server.util.TestData;

/**
 * Default implementation for <code>UserService interface</code>.
 */
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {

	@Autowired
	private UserDAO userDAO;
	@Autowired
	private RoleDAO roleDAO;
	@Autowired
	private SettingDAO settingDAO;
	@Autowired
	private MessageSource messageSource;
	@Autowired
	private RattsSessionRegistry sessionRegistry;
	private Logger log = LoggerFactory.getLogger(UserServiceImpl.class);

	@Override
	@Transactional(readOnly = true)
	@Secured("Perm_View_Users")
	public List<User> getUsers() {
		return userDAO.getUsers();
	}

	@Override
	@Transactional(readOnly = true)
	@Secured({"Perm_View_Users", "Perm_List_Users"})
	public PagingLoadResult<User> getUsers(PagingLoadConfig pagingLoadConfig) {
		log.info("Call reached the server on call to get Users");
		//return userDAO.findAllByPage(pagingLoadConfig, "name");
		List<User> users = TestData.getUsers();
		return new PagingLoadResult<User>(users, 10, users.size(), 20);
	}

	@Override
	@Transactional(readOnly = true)
	// note: no security on this method (can be reviewed)
	public User getLoggedInUser() throws RattsSessionExpiredException {
		try {
			// load a copy
			return findUserByUsername(RattsSecurityUtil.getLoggedInUser().getUsername());
		} catch (UserNotFoundException ex) {
			throw new UnexpectedException(
				"User logged in, but is not in database");
		}
	}

	private void verifyUserIsNotNull(User user, String criteria, String value)
		throws UserNotFoundException {
		if (user == null) {
			throw new UserNotFoundException(criteria, value);
		}
	}

	@Override
	@Transactional(readOnly = true)
	// note: no security for this method because it is used in authentication
	public User findUserByUsername(String username)
		throws UserNotFoundException {
		User user = userDAO.getUser(username);
		verifyUserIsNotNull(user, "username", username);
		return user;
	}

	@Override
	@Transactional(readOnly = true)
	// note: no security for this method because it is used by reset password
	// functionality
	public User findUserByEmail(String email) throws UserNotFoundException {
		User user = userDAO.findUserByEmail(email);
		verifyUserIsNotNull(user, "email", email);
		return user;
	}

	@Override
	@Transactional(readOnly = true)
	// note: no security for now (can be reviewed). currently used in
	// DataImportServlet
	public User findUserByPhoneNo(String phoneNo) throws UserNotFoundException {
		User user = userDAO.findUserByPhoneNo(phoneNo);
		verifyUserIsNotNull(user, "phoneNo", phoneNo);
		return user;
	}

	@Override
	@Secured("Perm_Add_Users")
	public User saveUser(User user) {
		boolean newUser = user.getId().equals("0") ? true : false;
		checkAndSetUserLoginProperties(user);
		userDAO.saveUser(user);
		sessionRegistry.updateUserEntries(user);
		return user;
	}

	@Override
	@Secured({"Perm_Edit_My_User", "Perm_Add_Users"})
	public void saveMyUser(User user) {
		String loggedInUserName = RattsSecurityUtil.getLoggedInUser().getUsername();
		if (loggedInUserName.equals(user.getUsername())) {
			checkAndSetUserLoginProperties(user);
			userDAO.saveUser(user);
			sessionRegistry.updateUserEntries(user);
		} else {
			throw new RattsSecurityException("User " + user.getUsername()
				+ " is not currently logged in user (" + loggedInUserName
				+ ")");
		}
	}

	private void checkAndSetUserLoginProperties(User user) {
		user.setPassword(RattsSecurityUtil.encodeString(user.getPassword()));
	}


	@Override
	// note: used during reset forgotten password, so security should be
	// accessable to all users
	public void resetPassword(User user, int size) {
		log.debug("UserServiceImpl resetPassword");
		String password = UUID.randomUUID().toString();
		password = password.replace("-", "").substring(0, size);
		user.setPassword(password);
		saveUser(user);
	}

	@Override
	@Secured("Perm_Delete_Users")
	public void deleteUser(User user) {
		userDAO.deleteUser(user);
	}

	@Override
	@Transactional(readOnly = true)
	// note: no security - used during login for admin user
	public Boolean checkIfUserChangedPassword(User user) {
		return OpenXDataUtil.checkIfUserChangedPassword(user);
	}

	/**
	 * Takes a comma separated list of role names and converts it into a list of
	 * roles by doing a database lookup of the name.
	 * 
	 * @param user
	 *            The user to add the roles to
	 * @param roles
	 *            A comma separated list of role names
	 * @return a list error messages
	 **/
	private List<String> setRoles(User user, String roles) {
		List<String> errors = new ArrayList<String>();
		if (roles == null || roles.trim().isEmpty()) {
			log.debug("No roles for import user: " + user.getUsername());
			errors.add("No roles specified");
			return errors;
		}

		String[] roleArr = roles.split(",");
		for (String roleName : roleArr) {
			List<Role> dbRoles = roleDAO.getRolesByName(roleName.trim());
			if (dbRoles.size() == 1) {
				user.getRoles().add(dbRoles.get(0));
			} else if (dbRoles.size() > 1) {
				errors.add("More than one role matched role name: " + roleName);
			} else if (dbRoles.isEmpty()) {
				errors.add("No role matched role name: " + roleName);
			}
		}
		return errors;
	}

	private boolean validate(User user, List<String> errors) {
		if (user.getUsername().isEmpty()) {
			errors.add("name is empty");
		}

		if (user.getPassword().isEmpty()) {
			errors.add("clearTextPassword is empty");
		}

		try {
			findUserByUsername(user.getUsername());
			errors.add("User with same username already exists");
		} catch (UserNotFoundException userShouldNotExist) {
			// ignore
		}

		return errors.isEmpty();
	}

	@Override
	// note: no security required for logout
	public void logout() {
		// Clear the Security Context
		if (SecurityContextHolder.getContext() != null) {
			String currentSession = RattsSecurityUtil.getCurrentSession();
			sessionRegistry.removeSessionInformation(currentSession + "");
			SecurityContextHolder.clearContext();
		}
	}

	@Override
	@Secured("Perm_Add_Users")
	public void saveUsers(List<User> users) {
		for (User user : users) {
			saveUser(user);
		}
	}

	@Override
	@Secured("Perm_Delete_Users")
	public void deleteUsers(List<User> users) {
		for (User user : users) {
			deleteUser(user);
		}
	}
}
